﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Un4seen.Bass;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace WhatsPlayingClient.Audio
{
	class AudioFile : IDisposable
	{
		#region Private fields

		bool disposed = false;
		Spectrum[] spectrums;

		#endregion

		public TagLib.File TagLibFile { get; private set; }
		public Spectrum[] Spectrums
		{
			get
			{
				if (spectrums == null)
					spectrums = GetSpectrums(BandSet.Presets);
				return spectrums;
			}
		}

		public TimeSpan ProcessingDuration { get; private set; }

		#region Construction/destruction

		public AudioFile(TagLib.File tagLibFile)
		{
			TagLibFile = tagLibFile;
#if DIRECTSHOW
			new ConverterGraph(tagLibFile.Name);
#endif
		}

		~AudioFile()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);
		}

		protected void Dispose(bool disposing)
		{
			if (!disposed)
			{
				if (disposing)
				{
					if(TagLibFile != null)
						TagLibFile.Dispose();
					TagLibFile = null;
				}
				disposed = true;
			}
		}

		#endregion

		#region Private methods

		private short[] GetPCMData()
		{
			var sw = Stopwatch.StartNew();
			try
			{
				short[] pcmData = new short[TagLibFile.Properties.AudioSampleRate * (int)(TagLibFile.Properties.Duration.TotalSeconds + 1)];
				short samplesPerSecond = 8000;

				int stream = Bass.BASS_StreamCreateFile(TagLibFile.Name, 0, 0, BASSFlag.BASS_DEFAULT | BASSFlag.BASS_MUSIC_PRESCAN | BASSFlag.BASS_MUSIC_DECODE | BASSFlag.BASS_SAMPLE_FLOAT);
				int res = Bass.BASS_ChannelGetData(stream, pcmData, pcmData.Length);
				//Un4seen.Bass.AddOn.Mix.BassMix.BASS_Mixer_ChannelFlags
				Bass.BASS_StreamFree(stream);
				//new ConverterGraph(TagLibFile.Name);
				return pcmData;
			}
			finally
			{
				sw.Stop();
				AddProcessingTime(sw);
			}
		}

		private void AddProcessingTime(Stopwatch sw)
		{
			ProcessingDuration = ProcessingDuration.Add(TimeSpan.FromMilliseconds(sw.ElapsedMilliseconds));
		}

		private Spectrum[] GetSpectrums(BandSet[] sets)
		{
			var sw = Stopwatch.StartNew();
			try
			{
				var spectrums = new Spectrum[sets.Length];
				for (int i = 0; i < sets.Length; ++i)
					spectrums[i] = new Spectrum(sets[i]);

				int channel = Bass.BASS_StreamCreateFile(TagLibFile.Name, 0, 0, BASSFlag.BASS_DEFAULT | BASSFlag.BASS_MUSIC_PRESCAN | BASSFlag.BASS_MUSIC_DECODE);
				//int length = (int)Bass.BASS_ChannelSeconds2Bytes(channel, 0.1);
				var fftSizeFlag = BASSData.BASS_DATA_FFT2048;
				int fftLen;

				switch (fftSizeFlag)
				{
					case BASSData.BASS_DATA_FFT256:   fftLen = 128; break;
					case BASSData.BASS_DATA_FFT512:   fftLen = 256; break;
					case BASSData.BASS_DATA_FFT1024:  fftLen = 512; break;
					case BASSData.BASS_DATA_FFT2048:  fftLen = 1024; break;
					case BASSData.BASS_DATA_FFT4096:  fftLen = 2048; break;
					case BASSData.BASS_DATA_FFT8192:  fftLen = 4096; break;
					case BASSData.BASS_DATA_FFT16384: fftLen = 8192; break;
					default: throw new NotSupportedException("fft size flag not supported");
				}

				float[] data = new float[fftLen];
				double bytesPerSec = (double)Bass.BASS_ChannelSeconds2Bytes(channel, 1);
				int samplesPerSec = (int)Bass.BASS_ChannelSeconds2Bytes(channel, 1) / 4;
				long streamPos = 0;
				
				GCHandle hGC = GCHandle.Alloc(data, GCHandleType.Pinned);
				try
				{
					for (double streamPositionSeconds = 0; ; streamPositionSeconds += 0.1)
					{
						if (!Bass.BASS_ChannelSetPosition(channel, streamPositionSeconds))
							break;

						// get the data
						int curLength = Bass.BASS_ChannelGetData(channel, hGC.AddrOfPinnedObject(),
								(int)(fftSizeFlag | BASSData.BASS_DATA_FFT_REMOVEDC | BASSData.BASS_DATA_FLOAT));

						if (curLength <= 0)
							break;
						
						foreach (var spc in spectrums)
//							spc.AddSpectrum(streamPos / bytesPerSec, samplesPerSec, data, fftLen);
							spc.AddSpectrum(streamPositionSeconds, samplesPerSec, data, fftLen);

						streamPos += curLength;
					}

					foreach (var spc in spectrums)
						spc.Cleanup();

					return spectrums;
				}
				finally
				{
					hGC.Free();
					Bass.BASS_StreamFree(channel);
				}
			}
			finally
			{
				sw.Stop();
				AddProcessingTime(sw);
			}
		}

		#endregion
	}
}
